package com.jxpanda.starter.config.security;


import com.jxpanda.starter.config.security.authentication.TokenAuthenticationManager;
import com.jxpanda.starter.config.security.authentication.TokenSecurityContextRepository;
import com.jxpanda.starter.config.security.token.AbstractTokenHelper;
import com.jxpanda.starter.config.security.token.HMACHelper;
import com.jxpanda.starter.config.security.token.RSAHelper;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.context.ServerSecurityContextRepository;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author Panda
 */
@Slf4j
@Configuration
@EnableWebFluxSecurity
@EnableConfigurationProperties(AuthenticationProperties.class)
public class WebFluxSecurityConfig {

    @Resource
    private ReactiveUserDetailsService userDetailsService;

    @Resource
    private final AuthenticationProperties authenticationProperties;

    public WebFluxSecurityConfig(AuthenticationProperties authenticationProperties) {
        this.authenticationProperties = authenticationProperties;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AbstractTokenHelper tokenHelper() {
        SignatureAlgorithm algorithm = authenticationProperties.getAlgorithm();
        if (AbstractTokenHelper.usingKeyPair(algorithm)) {
            // 暂时只有RSA签名类的需要特殊处理
            return new RSAHelper(authenticationProperties);
        }
        // 默认返回hmac类型，其他类型以后用到再说了
        return new HMACHelper(authenticationProperties);
    }

    @Bean
    public ReactiveAuthenticationManager authenticationManager() {
        return new TokenAuthenticationManager(userDetailsService, passwordEncoder());
    }

    @Bean
    public ServerSecurityContextRepository securityContextRepository() {
        return new TokenSecurityContextRepository(tokenHelper(), authenticationManager());
    }

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity httpSecurity) {
        List<String> openUrls = authenticationProperties.getOpenUrls();
        openUrls.add("/auth/**");
        openUrls.add("/open/**");
        return httpSecurity.csrf().disable()
                .formLogin().disable()
                .httpBasic().disable()
                .logout().disable()
                .securityContextRepository(securityContextRepository())
                .authorizeExchange(authorizeExchange -> authorizeExchange
                        // 放行option请求
                        .pathMatchers(HttpMethod.OPTIONS, "/**")
                        .permitAll()
                        // 外部配置的开放接口放行
                        .pathMatchers(openUrls.toArray(new String[]{}))
                        .permitAll()
                        // swagger的资源放行
                        .pathMatchers("/swagger-resources/**",
                                "/v3/api-docs/**",
                                "/csrf/**")
                        .permitAll()
                        // 其他所有接口全都需要授权
                        .anyExchange()
                        .authenticated())
                .build();
    }

}
